package com.seu.spring.advance.aop;

import com.seu.spring.advance.ioc.BeanPostProcessor;
import com.seu.spring.advance.ioc.factory.BeanFactory;
import com.seu.spring.advance.ioc.factory.BeanFactoryAware;
import com.seu.spring.advance.ioc.xml.XmlBeanFactory;
import org.aopalliance.intercept.MethodInterceptor;

import java.util.List;

/**
 * 实现了BeanFactoryAware接口：这个接口提供了对 BeanFactory 的感知，这样，尽管它是容器中的一个 Bean，却
 * 可以获取容器的引用，进而获取容器中所有的切点对象，决定对哪些对象的哪些方法进行代理。
 * 解决了为哪些对象提供 AOP 的植入的问题。
 *
 * @author liangfeihu
 * @since 2018/12/28 18:01
 */
public class AspectJAwareAdvisorAutoProxyCreator implements BeanPostProcessor, BeanFactoryAware {

    private XmlBeanFactory xmlBeanFactory;

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws Exception {
        return bean;
    }

    /**
     * 可以去看看AbstractBeanFactory的getBean()的实现
     * bean实例化后要进行初始化操作，会经过这个方法满足条件则生成相关的代理类并返回
     */
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws Exception {
        // 这里两个 if 判断很有必要，如果删除将会使程序进入死循环状态，
        // 最终导致 StackOverflowError 错误发生
        // 如果是切点通知器，则直接返回
        if (bean instanceof AspectJExpressionPointcutAdvisor) {
            return bean;
        }
        // 如果bean是方法拦截器，则直接返回
        if (bean instanceof MethodInterceptor) {
            return bean;
        }

        // 1. 从 BeanFactory 查找 AspectJExpressionPointcutAdvisor 类型的对象;
        // 通过getBeansForType方法加载BeanFactory中所有的PointcutAdvisor(保证了PointcutAdvisor的实例化顺序优于普通Bean);
        // AspectJ方式实现织入, 这里它会扫描所有Pointcut，并对bean做织入。
        List<AspectJExpressionPointcutAdvisor> advisors =
                xmlBeanFactory.getBeansForType(AspectJExpressionPointcutAdvisor.class);
        for (AspectJExpressionPointcutAdvisor advisor : advisors) {
            // 2. 使用 Pointcut 对象匹配当前 bean 对象
            // 匹配要拦截的类
            // 使用AspectJExpressionPointcut的matches匹配器，判断当前对象是不是要拦截的类的对象。
            if (advisor.getPointcut().getClassFilter().matchers(bean.getClass())) {
                // ProxyFactory继承了AdvisedSupport，所以内部封装了TargetSource和MethodInterceptor的元数据对象
                ProxyFactory advisedSupport = new ProxyFactory();
                // 设置切点的方法拦截器
                advisedSupport.setMethodInterceptor((MethodInterceptor) advisor.getAdvice());
                // 设置切点的方法匹配器
                // 利用AspectJ表达式进行方法匹配
                // AspectJExpressionPointcutAdvisor里的AspectJExpressionPointcut的getMethodMatcher()方法
                advisedSupport.setMethodMatcher(advisor.getPointcut().getMethodMatcher());

                // 是要拦截的类, 生成一个TargetSource(要拦截的对象和其类型)(被代理对象)
                TargetSource targetSource = new TargetSource(bean, bean.getClass(), bean.getClass().getInterfaces());
                advisedSupport.setTargetSource(targetSource);

                // 3. 交给实现了AopProxy接口的getProxy方法的ProxyFactory去生成代理对象，并返回
                // 代理生成器将切面逻辑Advice织入bean相关方法中，并为目标bean生成代理对象
                return advisedSupport.getProxy();
            }
        }

        // 2. 匹配失败，返回 bean
        return bean;
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws Exception {
        xmlBeanFactory = (XmlBeanFactory) beanFactory;
    }
}
